# coding=utf-8

import string
import random
import hashlib
import json
import datetime
import logging
from flask import current_app as app
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import synonym
from werkzeug import check_password_hash, generate_password_hash
from sqlalchemy.ext.declarative import AbstractConcreteBase
from over.handlers.helper import random_digital, random_str, random_printer
from over.ssh.client import Client

from .app import app
from flask_login import UserMixin

db = SQLAlchemy(app)

now = datetime.datetime.now
logger = logging.getLogger("model")


def one_day_later():
    return now() + datetime.timedelta(days=1)


def thirty_minte_later():
    return now() + datetime.timedelta(hours=0.5)


def before(start, end):
    return (start - end).microseconds < 0


def after(start, end):
    return (start - end).microseconds > 0


def reset_code(user_id):
    v = ''.join([random.choice(string.letters) for i in range(10)])
    m = hashlib.sha1()
    m.update(v)
    m.update(str(user_id))
    return m.hexdigest()


def random_index():
    return ''.join([random.choice(string.letters) for i in range(20)])


Model = db.Model


class Role(object):
    normal = 'normal'
    admin = 'admin'

class User(Model, UserMixin):
    __tablename__ = 'user'

    STATUS_REGISTER = 0
    STATUS_ACTIVATE = 1
    STATUS_DISABLE = 2
    STATUS_RESET = 3

    id = db.Column(db.Integer, primary_key=True)
    # username = db.Column(db.String)
    email = db.Column(db.String(64), index=True)
    _password = db.Column('_password', db.String(128))
    mobile = db.Column(db.String(64), default="")
    code = db.Column(db.String(64), nullable=True)  # 激活码
    expire = db.Column(db.DateTime, nullable=True)
    status = db.Column(db.Integer, default=0)  # status: 0,注册, 1:激活, 2:禁用
    balance = db.Column(db.Integer, default=0)  # 账户余额
    discount = db.Column(db.Integer, default=0)  # 折扣额度
    role = db.Column(db.String(32), default='normal')  # 角色
    gmt_created = db.Column(db.DateTime, default=now)
    gmt_modified = db.Column(db.DateTime, default=now, onupdate=now)

    cart_items = db.relationship('CartItem', backref='user',
                                 lazy='dynamic')
    accounts = db.relationship('ShadowsocksAccount', backref='user',
                               lazy='dynamic')

    def is_valid_password(self, password):
        return check_password_hash(self._password, password)

    @property
    def password(self):
        return self._password

    @password.setter
    def password(self, value):
        self._password = generate_password_hash(value)

    password = synonym("_password", descriptor=password)

    @classmethod
    def get_by_email(cls, email):
        return cls.query.filter_by(email=email).first()

    @classmethod
    def get_by_code(cls, code):
        return cls.query.filter_by(code=code, status=User.STATUS_RESET).filter(cls.expire >= now()).first()

    def forgot_password(self):
        self.code = reset_code(self.id)
        self.expire = thirty_minte_later()
        self.status = User.STATUS_RESET
        db.session.add(self)
        db.session.commit()

    def charge(self, amount):
        self.balance += amount
        db.session.add(self)
        db.session.commit()

    def pay(self, amount):
        if self.balance < amount:
            return False
        self.balance = self.balance - amount
        # db.session.add(self)
        # db.session.commit()
        return True

    @property
    def account_count(self):
        return len(list(self.accounts))

    @property
    def name(self):
        return self.email.split('@')[0]

    def new_account(self, expire_days, server):
        # 如果已经有对应的账号,则不需要重新生成,修改下过期时间
        accounts = list(self.accounts)
        if not accounts:
            expire = now() + datetime.timedelta(days=expire_days)
            password = random_printer(10)
            port = server.next_valid_port()
            account = ShadowsocksAccount(name=self.name,
                                         port=port,
                                         password=password,
                                         encryption_type=Encryption.RC4MD5,
                                         expire=expire,
                                         ip=server.ip,
                                         user_id=self.id,
                                         server_id=server.id)
        else:
            account = list(accounts)[0]
            account.expire += datetime.timedelta(days=expire_days)

        return account


class Encryption(object):
    TABLE = 'table'
    RC4MD5 = 'rc4-md5'
    SALSA20 = 'salsa20'
    CHACHA20 = 'chacha20'
    ACE256CFB = 'ace-256-cfb'
    ACE192CFB = 'ace-192-cfb'
    ACE128CFB = 'ace-128-cfb'
    BFCFB = 'bf-cfb'
    CAST5CFB = 'cast5-cfb'
    DESCFB = 'des-cfb'
    RC2CFB = 'rc2-cfb'
    SEEDCFB = 'seed-cfb'


class Server(Model):
    __tablename__ = 'server'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128))
    ip = db.Column(db.String(32))
    location = db.Column(db.String(512))
    desc = db.Column(db.String(2048))

    accounts = db.relationship('ShadowsocksAccount', backref='server',
                               lazy='dynamic')

    @classmethod
    def user_random(cls, user):
        count = cls.query.count()
        index = random.randint(1, count)
        return cls.query.get(index)

    def next_valid_port(self):
        key = 'current_port/%s' % self.ip
        redis = app.redis

        port = redis.get(key)
        if port is None:
            port = 80
            redis.set(key, port)
            return port

        port = int(port)
        while True:
            port += 1
            account = ShadowsocksAccount.query.filter_by(server_id=self.id, port=port).first()
            if account is None:  # found valid
                break
        redis.set(key, port)
        return port

    def remote_notify(self, shadow_account, action='add'):
        account = ServerAccount.query.filter_by(server_id=self.id).first()
        if account is not None:
            client = Client(self.ip, account.port)
            client.connect(account.username, account.password)
        else:
            account = ServerPrivateKey.query.filter_by(server_id=self.id).first()
            if account is None:
                return False

            client = Client(self.ip, account.port)
            client.connect_private_key(account.username, account.private_key)

        data = {
            'port': shadow_account.port,
            'password': shadow_account.password,
            'action': action,
        }
        try:
            result = client.execute('/root/change_account', data)
        except StandardError as e:
            logger.error('execute client error {0}'.format(e))
        finally:
            client.close()
        return result


class ServerAccount(Model):
    __tablename__ = 'server_account'

    id = db.Column(db.Integer, primary_key=True)
    server_id = db.Column(db.Integer, db.ForeignKey('server.id'))
    username = db.Column(db.String(128))
    password = db.Column(db.String(64))
    port = db.Column(db.Integer, default=22)  # ssh默认账号端口
    protocol = db.Column(db.String(32), default='ssh')  # protocol


class ServerPrivateKey(Model):
    id = db.Column(db.Integer, primary_key=True)
    server_id = db.Column(db.Integer, db.ForeignKey('server.id'))
    username = db.Column(db.String(128))
    private_key = db.Column(db.Text)  # 登录使用的私钥
    port = db.Column(db.Integer, default=22)  # ssh默认账号端口
    gmt_created = db.Column(db.DateTime, default=now)
    gmt_modified = db.Column(db.DateTime, default=now, onupdate=now)


class Account(Model):
    __tablename__ = 'account'

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(128))
    password = db.Column(db.String(64))


class ShadowsocksAccount(Model):
    __tablename__ = 'shadowsocks_account'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128))  # 登录用户名
    ip = db.Column(db.String(128))  # ip地址
    port = db.Column(db.Integer, index=True)
    password = db.Column(db.String(32))
    encryption_type = db.Column(db.String(32))  # 加密算法
    expire = db.Column(db.DateTime)  # 到期时间
    status = db.Column(db.Integer, default=0)  # 0:未开通,1:开通

    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    server_id = db.Column(db.Integer, db.ForeignKey('server.id'), default=0)  # 当为0,表示任意服务器都支持

    gmt_created = db.Column(db.DateTime, default=now)
    gmt_modified = db.Column(db.DateTime, default=now, onupdate=now)

    @property
    def display_status(self):
        if self.status == 0:
            return u'未开通'
        else:
            return u'开通'

    @property
    def user(self):
        return User.query.get(self.user_id)


class ActionLog(Model):
    __tablename__ = 'action_log'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    date = db.Column(db.DateTime, default=now)
    content = db.Column(db.Text)
    gmt_created = db.Column(db.DateTime, default=now)
    gmt_modified = db.Column(db.DateTime, default=now, onupdate=now)


def get_day(period):
    return {
        Product.PERIOD_YEAR: 365,
        Product.PERIOD_MONTH: 31,
        Product.PERIOD_YEAR: 7,
        Product.PERIOD_DAY: 1,
    }.get(period)


class Product(Model):
    __tablename__ = 'product'

    PERIOD_YEAR = 0
    PERIOD_MONTH = 1
    PERIOD_WEEK = 2
    PERIOD_DAY = 3

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(128))  # 产品标题
    price = db.Column(db.Integer)  # 产品价格,单位分
    period = db.Column(db.Integer, default=1)  # 周期 0:年, 1:月, 2:星期, 3:天
    description = db.Column(db.Text)  # 描述

    @property
    def day(self):
        return get_day(self.period)


class Recharge(Model):
    PAY_WAITING = 0
    PAY_COMPLETE = 1
    PAY_ERROR = 2
    PAY_TIMEOUT = 3
    PAY_CANCEL = 4

    __tablename__ = 'recharge'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))  # 用户id
    pay_method = db.Column(db.Integer, default=0)  # 0:支付宝
    amount = db.Column(db.Integer)  # 金额
    status = db.Column(db.Integer, default=0)  # 状态, 0:等待支付 1:支付完成,2:支付失败,3:超时, 4:取消支付
    order_num = db.Column(db.String(64))  # 充值号码
    pay_num = db.Column(db.String(128), default='')  # 最后的支付流水号
    gmt_created = db.Column(db.DateTime, default=now)
    gmt_modified = db.Column(db.DateTime, default=now, onupdate=now)

    @property
    def display_status(self):
        status_info = {
            0: u'等待支付',
            1: u'支付完成',
            2: u'支付失败',
            3: u'超时',
            4: u'取消',
        }
        return status_info[self.status]

    @classmethod
    def latest_recharges(cls, user_id):
        return cls.query.filter_by(user_id=user_id).order_by(cls.gmt_created.desc()).limit(20).all()

    @classmethod
    def get_by_order_num(cls, order_num, user_id):
        return cls.query.filter_by(order_num=order_num, user_id=user_id).first()


class TradeRecord(Model):
    __tablename__ = 'trade_record'
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))  # 用户id
    pay_num = db.Column(db.String(128))  # 支付码
    amount = db.Column(db.Integer)  # 交易金额
    order_num = db.Column(db.String(64))  # 充值号码
    comment = db.Column(db.String(1024))  # 备注
    gmt_created = db.Column(db.DateTime, default=now)
    gmt_modified = db.Column(db.DateTime, default=now, onupdate=now)


class CartItem(Model):
    __tablename__ = 'cart_item'
    id = db.Column(db.Integer, primary_key=True)
    product_id = db.Column(db.Integer, db.ForeignKey('product.id'))

    title = db.Column(db.String(128))  # 产品标题
    price = db.Column(db.Integer)  # 产品价格,单位分
    period = db.Column(db.Integer, default=1)  # 周期 0:年, 1:月, 2:星期, 3:天
    description = db.Column(db.Text)  # 描述
    index = db.Column(db.String(32), index=True)

    # shop_cart_id = db.Column(db.Integer, db.ForeignKey('shop_cart.id'))
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    is_ordered = db.Column(db.Integer, default=0)
    is_deleted = db.Column(db.Integer, default=0)
    gmt_created = db.Column(db.DateTime, default=now)
    gmt_modified = db.Column(db.DateTime, default=now, onupdate=now)

    @classmethod
    def get_items(cls, user_id):
        return cls.query.filter_by(user_id=user_id, is_deleted=0, is_ordered=0).all()


class OrderItem(Model):
    __tablename__ = 'order_item'

    id = db.Column(db.Integer, primary_key=True)
    order_num = db.Column(db.String(64), index=True)
    title = db.Column(db.String(128))  # 产品标题
    price = db.Column(db.Integer)  # 产品价格,单位分
    period = db.Column(db.Integer, default=1)  # 周期 0:年, 1:月, 2:星期, 3:天
    # user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    description = db.Column(db.Text)  # 描述
    count = db.Column(db.Integer, default=1)  # 订单项数量
    gmt_created = db.Column(db.DateTime, default=now)
    gmt_modified = db.Column(db.DateTime, default=now, onupdate=now)

    @property
    def day(self):
        return get_day(self.period)


class Order(db.Model):
    __tablename__ = 'order'

    id = db.Column(db.Integer, primary_key=True)
    order_num = db.Column(db.String(64), index=True)  # 订单号
    price = db.Column(db.Integer)  # 订单金额
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), default=0)
    status = db.Column(db.Integer, default=0)  # 支付状态,0:待支付,1:支付中,2:支付完成,3:支付超时,4:超时支付完成, 5:取消, 6:重新发起支付
    is_deleted = db.Column(db.Boolean, default=False)  # 订单删除
    gmt_created = db.Column(db.DateTime, default=now)
    gmt_modified = db.Column(db.DateTime, default=now, onupdate=now)

    @property
    def order_items(self):
        return OrderItem.query.filter_by(order_num=self.order_num).all()

    @classmethod
    def new_order_num(cls):
        # 订单生成
        return now().strftime('%Y%m%d%H%M%S%f') + random_digital(3)

    @classmethod
    def get_orders(cls, user_id):
        return cls.query.filter_by(user_id=user_id, is_deleted=False).order_by(cls.gmt_created.desc()).all()

    @property
    def count(self):
        return OrderItem.query.filter_by(order_num=self.order_num).count()

    @classmethod
    def get_order(cls, order_num):
        order = cls.query.filter_by(order_num=order_num, is_deleted=0).first()
        return order

    @classmethod
    def cancel(cls, order_num):
        order = cls.get_order(order_num)
        if order is None:
            return False

        if order.status == 0:
            order.status = 5
            db.session.add(order)
            db.session.commit()
            return True
        else:
            return False

    def account_expire(self):
        expire_day = []
        for order_item in self.order_items:
            expire_day.append(order_item.day)
        return sum(expire_day)


class Bill(Model):
    __tablename__ = 'bill'

    id = db.Column(db.Integer, primary_key=True)
    pay_method = db.Column(db.Integer, default=0)  # 支付方式, 0:支付宝, 1:微信
    pay_num = db.Column(db.String(64))  # 支付流水号
    order_num = db.Column(db.String(128))  # 当前订单号
    amount = db.Column(db.Integer)  # 金额
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), default=0)
    is_deleted = db.Column(db.Integer, default=0)
    gmt_created = db.Column(db.DateTime, default=now)
    gmt_modified = db.Column(db.DateTime, default=now, onupdate=now)

    @classmethod
    def exist_waiting_pay_bill(cls, user_id):
        count = cls.query.filter_by(user_id=user_id, status=0).count()
        return count > 0
